/*
 * Copyright 2006 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.spectaql.internal.metadata;

import org.spectaql.internal.util.CollectionUtils;
import org.spectaql.internal.util.Extractor;
import org.spectaql.metadata.EntityMetadata;
import org.spectaql.metadata.PersistenceMetadata;
import org.spectaql.metadata.PropertyMetadata;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * <code>PersistenceMetadataImpl</code> is the default implementation of the {@link PersistenceMetadata} interface.
 * </p>
 */
class PersistenceMetadataImpl implements PersistenceMetadata
{
    /**
     * <p>
     * Entity metadata keyed on entity class. 
     * </p>
     */
    private final Map<Class<?>, EntityMetadata> m_entityMetadataByClass;

    /**
     * <p>
     * Entity metadata keyed on entity name. 
     * </p>
     */
    private final Map<String, EntityMetadata> m_entityMetadataByName;

    /**
     * <p>
     * Property metadata keyed on accessor method.
     * </p>
     */
    private final Map<Method, PropertyMetadata> m_propertyMetadataByReadMethod;

    /**
     * <p>
     * Property metadata keyed on mutator method.
     * </p>
     */
    private final Map<Method, PropertyMetadata> m_propertyMetadataByWriteMethod;

    /**
     * <p>
     * Map of persistence entity names to the corresponding entity classes. 
     * </p>
     */
//    private final Map<String, Class<?>> m_entityNames;

    /**
     * <p>
     * Construct a {@link PersistenceMetadataImpl} instance.
     * </p>
     *
     * @param entityMetadata the entity metadata.
     */
    public PersistenceMetadataImpl(final Set<EntityMetadata> entityMetadata)
    {
        super();
        assert null != entityMetadata : "The [entityMetadata] argument cannot be null.";

        // Combine all property metadata into a single collection.
        final Collection<PropertyMetadata> allPropertyMetadata = CollectionUtils.combine(entityMetadata,
                new Extractor<Collection<PropertyMetadata>, EntityMetadata>()
                {
                    public Collection<PropertyMetadata> extract(final EntityMetadata object)
                    {
                        return object.getProperties();
                    }
                });

        // Build the various metadata maps.
        m_entityMetadataByClass = CollectionUtils.toMap(entityMetadata, new Extractor<Class<?>, EntityMetadata>()
        {
            public Class<?> extract(final EntityMetadata object)
            {
                return object.getEntityClass();
            }
        });
        m_entityMetadataByName = CollectionUtils.toMap(entityMetadata, new Extractor<String, EntityMetadata>()
        {
            public String extract(final EntityMetadata object)
            {
                return object.getEntityName();
            }
        });
        m_propertyMetadataByReadMethod = CollectionUtils.toMap(allPropertyMetadata,
                new Extractor<Method, PropertyMetadata>()
        {
            public Method extract(final PropertyMetadata object)
            {
                return object.getReadMethod();
            }
        });
        m_propertyMetadataByWriteMethod = CollectionUtils.toMap(allPropertyMetadata,
                new Extractor<Method, PropertyMetadata>()
        {
            public Method extract(final PropertyMetadata object)
            {
                return object.getWriteMethod();
            }
        });
    }

    /* inherit javadoc */
    public Iterator<String> getEntityNames()
    {
        return m_entityMetadataByName.keySet().iterator();
    }

    /* inherit javadoc */
    public EntityMetadata getEntity(final Class<?> entityClass)
    {
        if (!m_entityMetadataByClass.containsKey(entityClass))
        {
            throw new IllegalArgumentException("Entity class [" + entityClass.getName() +
                    "] is not mapped in the persistence context.");
        }
        return m_entityMetadataByClass.get(entityClass);
    }

    /* inherit javadoc */
    public EntityMetadata getEntity(final String entityName)
    {
        if (!m_entityMetadataByName.containsKey(entityName))
        {
            throw new IllegalArgumentException("Entity [" + entityName + "] is not mapped in the persistence context.");
        }
        return m_entityMetadataByName.get(entityName);
    }

    /* inherit javadoc */
    public PropertyMetadata getPropertyForReadMethod(final Method method) throws IllegalArgumentException
    {
        if (!m_propertyMetadataByReadMethod.containsKey(method))
        {
            throw new IllegalArgumentException("Method [" + method + "] is not a getter for a mapped entity class.");
        }
        return m_propertyMetadataByReadMethod.get(method);
    }

    /* inherit javadoc */
    public PropertyMetadata getPropertyForWriteMethod(final Method method) throws IllegalArgumentException
    {
        if (!m_propertyMetadataByWriteMethod.containsKey(method))
        {
            throw new IllegalArgumentException("Method [" + method + "] is not a setter for a mapped entity class.");
        }
        return m_propertyMetadataByWriteMethod.get(method);
    }

    /* inherit javadoc */
    public boolean isEntityClass(final Class<?> clazz)
    {
        return m_entityMetadataByClass.containsKey(clazz);
    }

    /* inherit javadoc */
    public boolean isEntityIdentifierClass(final Class<?> entityClass, final Class<?> clazz)
            throws IllegalArgumentException
    {
        // Fail if this is not a mapped class.
        if (!isEntityClass(entityClass))
        {
            throw new IllegalArgumentException(String.format("%s is not a mapped entity class.", entityClass));
        }

        // Get the identifier class from the entity metadata.
        final Class<?> identifierClass = m_entityMetadataByClass.get(entityClass).getIdentifierClass();
        return identifierClass.isAssignableFrom(clazz);
    }
}

